struct Ray
{
	float3 origin;
	float pad1;
	float3 direction;
	float pad2;
};

interface Object
{
	float FindIntersection(Ray ray);
	float3 GetNormalAt(float3 coord);
	float4 GetColourRGB();
	float GetGloss();
};

//struct Triangle : Object
//{
//	float FindIntersection(Ray ray)
//	{
//		return 10.0f;
//	}
//
//	float oro;
//};


struct CSCamera
{
	float3 position;
	float pad1;
	float3 direction;
	float pad2;
	float3 right;
	float pad3;
	float3 down;
	float pad4;
};

struct Sphere : Object
{
	float FindIntersection(Ray r)
	{
		float3 rayOrigin = r.origin;
		float3 rayDirection = r.direction;

		float a = 1.0;
		float b = (2.0 * (rayOrigin.x - position.x) * rayDirection.x) +
					(2.0 * (rayOrigin.y - position.y) * rayDirection.y) +
					(2.0 * (rayOrigin.z - position.z) * rayDirection.z);

		float c = (pow(rayOrigin.x - position.x, 2.0)) + 
					(pow(rayOrigin.y - position.y, 2.0)) + 
					(pow(rayOrigin.z - position.z, 2.0)) - 
					(radius * radius);

		float discriminant = b * b - 4.0 * c;

		if (discriminant > 0.0)
		{
			float root1 = (((-b) - sqrt(discriminant)) / 2.0) - 0.00001;
			
			if (root1 > 0.0)
			{
				return root1;
			}
			else
			{
				float root2 = ((sqrt(discriminant) - b) / 2.0) - 0.00001;
				return root2;
			}
		}
		else
		{
			return -1.0;
		}
	}

	float3 GetNormalAt(float3 coord)
	{
		float3 normal = coord - position;
		normal = normalize(normal);
		return normal;
	}

	float4 GetColourRGB()
	{
		return colour;
	}

	float GetGloss()
	{
		return gloss;
	}

	float4 colour;
	float gloss;
	float3 pad3;
	float3 position;
	float pad1;
	float radius;
	float3 pad2;
};

struct Plane : Object
{
	float FindIntersection(Ray ray)
	{
		float dotProd = dot(ray.direction, normal);
		if (dotProd == 0)
		{
			// ray = parrallel to plane
			return -1.0f;
		}

		float3 toAdd = -(normal * distance);
		float3 rhsDot = ray.origin + toAdd;

		float dotNormal = dot(normal, rhsDot);
		float result = (dotNormal / dotProd) * -1.0f;
		return result;
	}

	float3 GetNormalAt(float3 coord)
	{
		return normal;
	}
	
	float4 GetColourRGB()
	{
		return colour;
	}

	float GetGloss()
	{
		return gloss;
	}

	float4 colour;
	float gloss;
	float3 pad3;
	float3 normal;
	float pad1;
	float distance;
	float3 pad2;
};

// lighting
struct LightGlobal
{
	float3 position;
	float pad1;
	float4 colour;
};

// objects struct
//struct CSObjects
//{
//	Sphere sphere;
//};

cbuffer cbData : register (b0)
{
	Sphere sphere;
	CSCamera Camera;
	LightGlobal Light;
};

//// change sphere later for more objects
//float4 CalculateColour(float3 intersectionPoint, float3 intersectionDir, int finalObjectIndex, Object objects[2], LightGlobal lightSource,
//					   float accuracy, float ambientLight)
//{
//	float4 intersectedObjectColour = sphere.colour;
//	float3 objectNormal = sphere.GetNormalAt(intersectionPoint);
//	// add the rest of calculate colour function
//
//	float4 finalColour = sphere.colour * ambientLight;
//	float3 lightDirection = lightSource.position - intersectionPoint;
//	lightDirection = normalize(lightDirection);
//
//	float angle = dot(objectNormal, lightDirection);
//	if (angle > 0.0)
//	{
//		// testing if shadow
//		// not used until more objects can be added
//		bool inShadow = false;
//		
//		float3 lightVec = lightDirection;
//		float disFromLight = length(lightDirection);
//
//		float3 shadowRayDirection = lightDirection;
//		Ray shadowRay;
//		shadowRay.origin = intersectionPoint;
//		shadowRay.direction = shadowRayDirection;
//
//		//// test is shadow ray intersected with plane
//		//float planeIntersection = plane.FindIntersection(shadowRay);
//		//if (planeIntersection > accuracy)
//		//{
//		//	if (planeIntersection <= disFromLight)
//		//	{
//		//		inShadow = true;
//		//	}
//		//}
//
//		float shadowIntersections[2];
//		[unroll(2)] for (int i = 0; i != 2; i++)
//		{
//			shadowIntersections[i] = objects[i].FindIntersection(shadowRay);
//		}
//
//		[unroll(2)] for (i = 0; i != 2; i++)
//		{
//			if (shadowIntersections[i] > accuracy)
//			{
//				if (shadowIntersections[i] <= disFromLight)
//				{
//					inShadow = true;
//				}
//				break;
//			}
//		}
//
//		if (!inShadow)
//		{
//			// for each object test if the ray intersects it
//			// for each intersected points, if it's greater than the accuracy 
//			// and is smaller than the distance from the light, then pixel is in shadow, break;
//
//			// if is not in shadow then calculate final colour
//
//			finalColour += objects[finalObjectIndex].GetColourRGB() * (lightSource.colour * angle);
//
//			float intersectedObjectGloss = objects[finalObjectIndex].GetGloss();
//			if (intersectedObjectGloss > 0.0 && intersectedObjectGloss < 1.0)
//			{
//				float dotProd = dot(objectNormal, intersectionDir);
//				float3 scalar1 = objectNormal * dotProd;
//				float3 final1 = scalar1 + intersectionDir;
//
//				float3 scalar2 = final1 * 2.0;
//				float3 final2 = scalar2 + intersectionDir;
//
//				float3 reflectionDirection = normalize(final2);
//
//				float specular = dot(reflectionDirection, intersectionDir);
//				if (specular > 0.0)
//				{
//					specular = pow(specular, 800.0);
//					finalColour += lightSource.colour * specular * intersectedObjectGloss;
//				}
//			}
//		}
//	}
//
//
//	return finalColour;
//}

//// change sphere later for more objects
//float4 CalculateColourPlane(float3 intersectionPoint, float3 intersectionDir, Sphere sphere, Plane plane, LightGlobal lightSource,
//					   float accuracy, float ambientLight)
//{
//	float4 intersectedObjectColour = plane.colour;
//	float3 objectNormal = plane.GetNormalAt(float3(0, 0, 0));
//	// add the rest of calculate colour function
//
//	float4 finalColour = intersectedObjectColour * ambientLight;
//
//	// reflection tests
//
//
//
//
//
//
//	float3 lightDirection = lightSource.position - intersectionPoint;
//	lightDirection = normalize(lightDirection);
//
//	float angle = dot(objectNormal, lightDirection);
//	if (angle > 0.0)
//	{
//		// testing if shadow
//		// not used until more objects can be added
//		bool inShadow = false;
//		
//		float3 lightVec = lightDirection;
//		float disFromLight = length(lightDirection);
//
//		float3 shadowRayDirection = lightDirection;
//		Ray shadowRay;
//		shadowRay.origin = intersectionPoint;
//		shadowRay.direction = shadowRayDirection;
//
//		// test is shadow ray intersected with sphere
//		float planeIntersection = sphere.FindIntersection(shadowRay);
//		if (planeIntersection > accuracy)
//		{
//			if (planeIntersection <= disFromLight)
//			{
//				inShadow = true;
//			}
//		}
//
//		if (!inShadow)
//		{
//			// for each object test if the ray intersects it
//			// for each intersected points, if it's greater than the accuracy 
//			// and is smaller than the distance from the light, then pixel is in shadow, break;
//
//			// if is not in shadow then calculate final colour
//
//			finalColour += plane.colour * (lightSource.colour * angle);
//
//			if (plane.gloss > 0.0 && plane.gloss < 1.0)
//			{
//				float dotProd = dot(objectNormal, intersectionDir);
//				float3 scalar1 = objectNormal * dotProd;
//				float3 final1 = scalar1 + intersectionDir;
//
//				float3 scalar2 = final1 * 2.0;
//				float3 final2 = scalar2 + intersectionDir;
//
//				float3 reflectionDirection = normalize(final2);
//
//				float specular = dot(reflectionDirection, intersectionDir);
//				if (specular > 0.0)
//				{
//					specular = pow(specular, 800.0);
//					finalColour += lightSource.colour * specular * sphere.gloss;
//				}
//			}
//		}
//	}
//
//
//	return finalColour;
//}

// debug buffer
struct DebugBuffer
{
	int called;
	int x;
	int y;
	float intersection;
};

Texture2D<float4> input : register(t0);
RWTexture2D<float4> result : register(u0);
RWStructuredBuffer<DebugBuffer> debugOut: register (u1);

#define NUM_OF_THREADS_X 1
#define NUM_OF_THREADS_Y 1

[numthreads(NUM_OF_THREADS_X, NUM_OF_THREADS_Y, 1)]
//[numthreads(32, 32, 1)]

void main(uint3 dispatchID : SV_DispatchThreadID, uint3 groupThreadID : SV_GroupThreadID)
{	
	//Triangle testing;
	//Object convertedTri;
	//convertedTri = testing;
	//Ray r;
	//debugOut[dispatchID.x].called = convertedTri.FindIntersection(r);
	

	float2 screenSize = float2(640, 480);
	float aspectRatio = 1.333333333333333;
	float2 amounts;
	amounts.x = ((dispatchID.x + 0.5) / screenSize.x) * aspectRatio - ((screenSize.x - screenSize.y) / screenSize.y / 2.0);
	amounts.y = ((screenSize.y - dispatchID.y) + 0.5) / screenSize.y;

	// ray calcs here
	float3 rayOrigin = Camera.position;
	float3 rayDirection = Camera.direction;
	rayDirection += (Camera.right * (amounts.x - 0.5)) + (Camera.down * (amounts.y - 0.5));
	rayDirection = normalize(rayDirection);
	//rayDirection *= -1.0;

	Ray ray;
	ray.origin = rayOrigin;
	ray.direction = rayDirection;
	
	//float4 testCol;
	//testCol.x = rayDirection.x;
	//testCol.y = rayDirection.y;
	//testCol.z = rayDirection.z;
	//testCol.w = 1.0;
	//result[dispatchID.xy] = testCol;
	//return;

	Plane plane;
	plane.colour = float4(0.8, 0.0, 0.0, 1.0);
	plane.gloss = 0.0f;
	plane.normal = float3(0.0, 1.0, 0.0);
	plane.distance = -1.0;

	Object objects[2];
	objects[0] = sphere;
	objects[1] = plane;

	float intersections[2];

	int numOfIntersections = 0;

	[unroll(2)] for (int i = 0; i != 2; i++)
	{
		// find if any objects intersected,
		// -1 == no intersection
		intersections[i] = objects[i].FindIntersection(ray);
		if (intersections[i] != -1)
		{
			numOfIntersections++;
		}
	}
	
	int finalIntersectedObjectIndex = -1;
	if (numOfIntersections == 1)
	{
		if (intersections[0] > 0.0f)
		{
			finalIntersectedObjectIndex = 0;
		}
	}
	else
	{
		// find the closest vector to camera
		float minVal = 10000.0f; // culled dist;
		int index = 0;
		[unroll] for (int i = 0; i != numOfIntersections; i++)
		{
			if (minVal > intersections[i] && intersections[i] != -1.0f && intersections[i] < 10000.0f)
			{
				minVal = intersections[i];
				index = i;
			}
		}

		if (minVal > 0.0f)
		{
			finalIntersectedObjectIndex = index;
		}
	}

	
	float4 finalColour = float4(0.0, 0.0, 0.0, 1.0);
	if (finalIntersectedObjectIndex == -1)
	{
		result[dispatchID.xy] = float4(0.0, 0.0, 0.0, 1.0); // bg colour
		return;
	}
	else
	{
		float accuracy = 0.000000;
		float ambientLight = 0.2;
		if (intersections[finalIntersectedObjectIndex] > accuracy)
		{
			float3 intersectionPoint = ray.origin + (ray.direction * intersections[finalIntersectedObjectIndex]);
			float3 intersectionDirection = ray.direction;

			//finalColour = CalculateColour(intersectionPoint, intersectionDirection, finalIntersectedObjectIndex, objects, Light, accuracy, ambientLight);
			


	float4 intersectedObjectColour = objects[finalIntersectedObjectIndex].GetColourRGB();
	float3 objectNormal = objects[finalIntersectedObjectIndex].GetNormalAt(intersectionPoint);
	// add the rest of calculate colour function

	float4 endColour = intersectedObjectColour * ambientLight;
	float3 lightDirection = Light.position - intersectionPoint;
	lightDirection = normalize(lightDirection);

	float angle = dot(objectNormal, lightDirection);
	if (angle > 0.0)
	{
		// testing if shadow
		// not used until more objects can be added
		bool inShadow = false;
		
		float3 lightVec = lightDirection;
		float disFromLight = length(lightDirection);

		float3 shadowRayDirection = lightDirection;
		Ray shadowRay;
		shadowRay.origin = intersectionPoint;
		shadowRay.direction = shadowRayDirection;
		
		float shadowIntersections[2];
		[unroll(2)] for (int i = 0; i != 2; i++)
		{
			shadowIntersections[i] = objects[i].FindIntersection(shadowRay);
		}

		[unroll(2)] for (i = 0; i != 2; i++)
		{
			if (shadowIntersections[i] > accuracy)
			{
				if (shadowIntersections[i] <= disFromLight)
				{
					inShadow = true;
				}
				break;
			}
		}

		if (!inShadow)
		{
			// for each object test if the ray intersects it
			// for each intersected points, if it's greater than the accuracy 
			// and is smaller than the distance from the light, then pixel is in shadow, break;

			// if is not in shadow then calculate final colour

			endColour += objects[finalIntersectedObjectIndex].GetColourRGB() * (Light.colour * angle);

			float intersectedObjectGloss = objects[finalIntersectedObjectIndex].GetGloss();
			if (intersectedObjectGloss > 0.0 && intersectedObjectGloss < 1.0)
			{
				float dotProd = dot(objectNormal, intersectionDirection);
				float3 scalar1 = objectNormal * dotProd;
				float3 final1 = scalar1 + intersectionDirection;

				float3 scalar2 = final1 * 2.0;
				float3 final2 = scalar2 + intersectionDirection;

				float3 reflectionDirection = normalize(final2);

				float specular = dot(reflectionDirection, intersectionDirection);
				if (specular > 0.0)
				{
					specular = pow(specular, 800.0);
					endColour += Light.colour * specular * intersectedObjectGloss;
				}
			}
		}
	}

			finalColour = endColour;

		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		}
	}

	result[dispatchID.xy] = finalColour;	
}